In the normal flow of control, each function that is called executes until it reaches a return point; under these conditions no special effort is needed to restore the environment as long as each function undoes any change that it makes to the dynamic state before it returns. When we make a non-local transfer, we skip a potentially arbitrary collection of these cleanup actions. Since we cannot in general know what changes have been made to the dynamic environment below us on the stack, we must restore a snapshot of the dynamic environment at the re-entry point.
We represent the closed continuation by the pointer to the unwind-block for the reentry point. At the exit point, we just pass this stack pointer to the Unwind VOP, which deals with processing any unwind-protects. When Unwind is done, it grabs the re-entry PC out of the location at the stack pointer and jumps in.
Catch and Unwind-Protect work in pretty much the same way. We make a stack TN to hold the catch frame or whatever, allocate TNs in them to represent the slots, and then initialize them. The frame can be explicitly linked in by TN manipulations, since the active catch and whatnot are represented by TNs. Since allocation of the frame is decoupled from linking and unlinking, some of this stuff could be moved out of loops. We will need a VOP for loading the PC for an arbitrary continuation so that we can set up the reentry PC. This can be done using the Call VOP. Using a call instruction is probably a good way to get a PC on most architectures anyway.
These TNs are allocated by Pack like any others; we use special alloc and dealloc VOPs to delimit the aggregate lifetimes.
In the non-local case, the the Block, Catch and Unwind-Protect special forms are implemented using unwind blocks. The unwind blocks are built by move operations emitted inline by the compiler. The compiler adds and removes catches and unwind protects by explicit moves to the locations that hold the current catch and unwind protect blocks. The entry PC is loaded using the Call VOP.
The Unwind miscop is the basis non-local exits. It takes the address of an unwind block and processes unwind-protects until the current unwind-protect is the one recorded in the unwind block, then jumps in at the entry in the unwind block. The entry for the unwind block is responsible for restoring any state other than the current unwind-protect.
Unwind is used directly to implement non-local Return-From. The address of the unwind block is stored in a closure variable.
Catch just does a scan up the chain of Catch blocks, starting at the current catch. When it finds the right one, it calls unwind on it.
Unwind-protects are represented by unwind blocks linked into the current unwind-protect chain. The cleanup code is entered just like any other any other unwind entry. As before, the entry is responsible for establishing the correct dynamic environment for the cleanup code. The target unwind block is passed in some non-argument register. When the cleanup code is done, it just calls Unwind with the block passed in. The cleanup code must be careful not to trash the argument registers or CS, since there may be multiple values lurking out there.
With Catch/Throw, we always use the variable values return value passing convention, since we don't know how many values the catch wants. With Block/Return-From, we can do whatever we want, since the returner and receiver know each other.
If a Block or Catch receives stack values, it must call a VOP that BLT's the values down the stack, squeezing out any intermediate crud.
unwind (context) throw (tag) Unwind does a non-local exit, unwinding to the place indicated by Context. Context is a pointer to a block of storage allocated on the control stack, containing the entry PC, current environment and current unwind-protect. We scan up the stack, processing unwind-protects until we reach the entry point. The values being returned are passed in the standard locations. Throw is similar, but does a dynamic lookup for the Tag to determine what context to unwind to.